package org.onionframework.persistence.service;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.commons.beanutils.PropertyUtils;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxyHelper;
import org.onionframework.core.util.Exceptions;
import org.onionframework.core.util.Reflections;
import org.onionframework.persistence.dao.GenericDao;
import org.onionframework.persistence.model.Model;
import org.onionframework.persistence.model.logic.Actor;
import org.onionframework.persistence.model.logic.Audit;
import org.onionframework.persistence.model.logic.Auditable;
import org.onionframework.persistence.model.logic.LogicalRemovable;
import org.onionframework.persistence.query.OnionQuery;
import org.onionframework.persistence.query.projection.Projections;
import org.onionframework.persistence.support.CurrentActorHolder;
import org.onionframework.persistence.support.EntityMetainfoManager;
import org.onionframework.persistence.support.EntityMetainfoManager.PropertyInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

/**
 * 通用服务接口实现
 * 
 * @author Yang XuePing
 * @author allan zhang
 */
public class GenericServiceImpl<T extends Model<? extends Serializable>> implements GenericService<T> {

	protected Class<T> entityClass = Reflections.getClassGenricType(this.getClass());
	
	protected GenericDao<T> dao;
	protected CurrentActorHolder currentActorHolder;
	@Autowired protected EntityMetainfoManager entityMetainfoService;
	protected boolean hasUniqueProperty = false;
	
	public Class<T> getEntityClass(){
		if(this.entityClass == null || this.entityClass.equals(Object.class)){
			Class<T> clazz;
			if(this.dao != null)
				clazz = this.dao.getEntityClass();
			else
				clazz = null;
			
			if(clazz != null)
				this.entityClass = clazz;
		}

		return this.entityClass;
	}
	
	@PostConstruct
	public void init(){
		if(this.getEntityClass() != null){
			Map<String,PropertyInfo> ups = this.getUniqueProperties(this.getEntityClass());
			if(ups == null)
				this.hasUniqueProperty = false;
			else
				this.hasUniqueProperty = !ups.isEmpty();
		}
	}
	
	protected Map<String,PropertyInfo> getUniqueProperties(Class<?> clazz){
		return this.entityMetainfoService.getUniqueProperties(clazz);
	}
	
	/**
	 * 检查属性值唯一
	 * 
	 * @param object 要保存的对象
	 * @param constraints 约束（属性名：属性值）
	 * @param message 不唯一的时候抛出的异常的消息
	 */
	protected void checkUnique(Model<?> object, String message, String... fields){
		Class<?> clazz = object.getClass();
		if(clazz.getName().indexOf("$$") > -1)
			clazz = HibernateProxyHelper.getClassWithoutInitializingProxy(object);
		
		OnionQuery query 
			= OnionQuery.instance(this.entityClass)
				.projections(Projections.instance().property("id"));
		try {
			for(String field : fields){
				query.add(field, PropertyUtils.getNestedProperty(object, field));
			}
		} catch (Exception e) {
			throw Exceptions.system("获取属性失败",e);
		}
		
		List<Serializable> exists = this.dao.findList(query.forClass(this.entityClass));
		
		if(exists.size() == 0)
			return;
		
		if(exists.size() > 1)
			throw new UniqueCheckFailureException("数据库中已经存在不符合约束的多条数据，请检查");
		
		if(exists.get(0).equals(object.getId()))
			return;
		else
			throw new UniqueCheckFailureException(message);
	}
	
	/**
	 * 通过id是否存在检查唯一约束
	 * @param object 要被检查的对象
	 * @param properties 要检查的属性及其中文含义的映射
	 */
	protected void checkUnique(Model<?> object){
		Assert.notNull(object);
		
		Class<?> clazz = object.getClass();
		if(clazz.getName().indexOf("$$") > -1)
			clazz = HibernateProxyHelper.getClassWithoutInitializingProxy(object);
		
		if(clazz.equals(this.getEntityClass()) && !this.hasUniqueProperty)
			return;
		
		Map<String,PropertyInfo> uniqueProperties = this.getUniqueProperties(clazz);
		
		if(uniqueProperties.isEmpty())
			return;
		
		Map<String,Object> parameters = new HashMap<String,Object>();
		
		Object value;
		
		try {
			for(Map.Entry<String, PropertyInfo> entry : uniqueProperties.entrySet()){
				value = PropertyUtils.getProperty(object, entry.getKey());
				if(value != null)
					parameters.put(entry.getKey(), value);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
		
		if(parameters.isEmpty())
			return;

		List<Serializable> uniqueCheckId = this.dao.findList(
				OnionQuery.orInstance(this.entityClass)
						.projections(Projections.instance().property("id"))
						.addAll(parameters));

		//没有不符合约束的数据
		if(uniqueCheckId == null || uniqueCheckId.size() == 0)
			return;
		
		//不符合约束的数据就只有自己
		if(object.getId() != null && uniqueCheckId.size() == 1 && uniqueCheckId.get(0).equals(object.getId()))
			return;
		
		StringBuilder message = new StringBuilder("已经存在");
		
		try {
			int i=0;
			for(Map.Entry<String, PropertyInfo> entry : uniqueProperties.entrySet()){
				if(parameters.containsKey(entry.getKey())){
					message.append(entry.getValue().getDisplayName()).append("为").append(PropertyUtils.getProperty(object, entry.getKey()));
					if(i< parameters.size() - 1)
						message.append("或者");
					i++;
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		message.append("的记录");
		
		throw new UniqueCheckFailureException(message.toString());
	}
	
	public void setEntityClass(Class<T> entityClass){
		this.entityClass = entityClass;
	}
	
    public void setDao(GenericDao<T> dao) {
        this.dao = dao;
    }
    
    /**
	 * @param currentActorHolder the currentActorHolder to set
	 */
    @Autowired(required=false)
	public void setCurrentActorHolder(CurrentActorHolder currentActorHolder) {
		this.currentActorHolder = currentActorHolder;
	}

	/**
     * 创建对象
     */
    public T create(){
    	try {
			T object = this.getEntityClass().newInstance();
			if(object instanceof Auditable){
				Audit audit = new Audit();
				((Auditable)object).setAudit(audit);
			}
			return object;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
    }

	/**
	 * 删除，其中包含了逻辑删除
	 */
    @SuppressWarnings("unchecked")
	@Override
    public void removeById(Serializable id) {
    	if(LogicalRemovable.class.isAssignableFrom(this.dao.getEntityClass())){
    		LogicalRemovable o = (LogicalRemovable)this.get(id);
    		o.setEnabled(false);
    		this.save((T)o);
    	}
    	else
    		dao.remove(this.load(id));
    }
    
    /**
	 * 删除，其中包含了逻辑删除
	 */
	@Override
    public void remove(T o){
    	if(o instanceof LogicalRemovable){
    		((LogicalRemovable) o).setEnabled(false);
    		this.save((T)o);
    	}
    	else
    		dao.remove(o);
    }

    @Override
    public T get(Serializable id) {
        return dao.get(id);
    }
	
	/**
	 * 加入逻辑删除相关的条件
	 */
	protected void processLogicalRemovable(OnionQuery query){
		if(LogicalRemovable.class.isAssignableFrom(this.dao.getEntityClass())){
			if(query == null)
    			query = OnionQuery.instance(this.entityClass).eq(LogicalRemovable.PROPERTY_NAME,true);
			else{
				Boolean seeLogicalRemovable = (Boolean)query.getAttribute(OnionQuery.ATTR_KEY_SEE_LOGICAL_REMOVED);
				if(seeLogicalRemovable == null || !seeLogicalRemovable)
		    		query.eq("enabled",true);
			}
    	}
	}

    @Override
    public List<T> find(OnionQuery query) {    	
    	this.processLogicalRemovable(query);
        return dao.find(query);
    }

    @Override
    public void save(T t) {
    	
    	this.checkUnique(t);
    	
    	if(t instanceof Auditable){
    		Audit audit = ((Auditable)t).getAudit();
    		if (audit == null) {
    			audit = new Audit();
    			((Auditable)t).setAudit(audit);
    		}
    		if(this.currentActorHolder != null){
    			Actor currentActor = this.currentActorHolder.getUser();
    			if(currentActor == null)
    				currentActor = this.currentActorHolder.getSystemActor();
    			
    			audit.setLastModifyUser(currentActor.getName());
    		}
    		audit.setLastModified(new Date());
    	}
    	
        dao.save(t);
    }

	/**
	 * 请注意Hibernate的get和load的区别
	 */
	@Override
	public T load(Serializable id) {
		return this.dao.load(id);
	}

	@Override
	public void refresh(Object o) {
		this.dao.refresh(o);
	}

	@Override
	public Long count(OnionQuery query) {
		this.processLogicalRemovable(query);
		return this.dao.count(query);
	}
	
	public List<T> findAll(){
		return this.dao.getAll();
	}
	
	@Override
	public T findUnique(String name, Object value) {
		return this.dao.findUnique(name, value);
	}

	@Override
	public T findUnique(OnionQuery query) {
		return this.dao.findUnique(query);
	}

	@Override
	public List<T> find(String name, Object value) {
		return this.dao.find(name, value);
	}

	public <E> List<E> findList(OnionQuery query){
		this.processLogicalRemovable(query);
		return this.dao.findList(query.forClass(this.entityClass));
	}
	
	public <E> List<E> findList(OnionQuery query, int start, int maxResults) {
		this.processLogicalRemovable(query);
		return this.dao.findList(query.forClass(this.entityClass));
	}

	@Override
	public void evict(Object o) {
		this.dao.evict(o);
	}
	
	public void initProxy(Object o){
		Hibernate.initialize(o);
	}

	@Override
	public void flush() {
		this.dao.flush();
	}

	@Override
	public void merge(Object o) {
		this.dao.merge(o);
	}

	/**
	 * 批量保存
	 * @param objects 要保存的对象集合
	 */
	@Override
	public void saveAll(Collection<T> objects) {
		this.dao.saveAll(objects);
	}
}
